<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>城市特效</title>
    <script type="importmap">
      {
        "imports": {
          "three": "./threejs/build/three.module.js",
          "three/addons/": "./threejs/examples/jsm/"
        }
      }
    </script>
    <style>
      body {
        margin: 0;
        padding: 1px;
        box-sizing: border-box;
        background: #1f1f1f;
        display: flex;
        flex-direction: column;
        width: 100vw;
        height: 100vh;
        overflow: hidden;
      }
      #box {
        width: 100%;
        height: 100%;
      }
    </style>
  </head>

  <body>
    <div id="box"></div>

    <script type="module">
      /* ==========  0. 基础准备  ========== */
      import * as THREE from "three";
      import { OrbitControls } from "three/addons/controls/OrbitControls.js";
      import { FBXLoader } from "three/addons/loaders/FBXLoader.js";
 

      const box = document.getElementById("box");
      const scene = new THREE.Scene();
      const camera = new THREE.PerspectiveCamera(
        50,
        box.clientWidth / box.clientHeight,
        0.1,
        100000
      );
      camera.position.set(0, 400, 1000);

      const renderer = new THREE.WebGLRenderer({
        antialias: true,
        alpha: true,
        logarithmicDepthBuffer: true,
      });
      renderer.setPixelRatio(window.devicePixelRatio * 1.3);
      renderer.setSize(box.clientWidth, box.clientHeight);
      box.appendChild(renderer.domElement);

      const controls = new OrbitControls(camera, renderer.domElement);
      controls.enableDamping = true;

      /* ==========  1. 天空盒  ========== */
      const urls = [0, 1, 2, 3, 4, 5].map(
        (k) => FILE_HOST + "textures/cube/skybox/" + (k + 1) + ".png"
      );
      scene.background = new THREE.CubeTextureLoader().load(urls);

      /* ==========  2. 灯光  ========== */
      scene.add(new THREE.AmbientLight(0xadadad));
      const dirLight = new THREE.DirectionalLight(0xffffff, 0.5);
      dirLight.position.set(600, 600, 0);
      scene.add(dirLight);

      /* ==========  3. Shader 工具函数  ========== */
      const renderList = [];

      /**
       * 对于shader内容的修改，需要根据具体内容进行处理
       * shader中会存在#include <begin_vertex>等语句，这些事three定义的glsl，具体脚本内容查看three源码中renderer/shaders/shaderChunk下对应脚本文件
       * 而修改shader就是在对应的脚本语句后修改脚本或增加语句
       */
      const applyGrowShader = (shader) => {
        shader.uniforms.uProgress = { value: 0 };
        shader.vertexShader = `
    uniform float uProgress;
    ${shader.vertexShader}
  `;
        shader.vertexShader = shader.vertexShader.replace(
          "#include <begin_vertex>",
          `
      #include <begin_vertex>
      transformed.z = position.z * min(uProgress, 1.0);
    `
        );
        renderList.push((progress) => {
          shader.uniforms.uProgress.value = progress;
        });
      };
      // 建筑表面流动上升效果
      const applyRiseShader = (shader) => {
        shader.uniforms.uRiseTime = { value: 0 };
        shader.uniforms.uRiseColor = { value: new THREE.Color("#87CEEB") };

        shader.vertexShader = shader.vertexShader.replace(
          "#include <common>",
          `
      #include <common>
      varying vec3 vTransformedNormal;
      varying float vHeight;
    `
        );
        shader.vertexShader = shader.vertexShader.replace(
          "#include <begin_vertex>",
          `
      #include <begin_vertex>
      vTransformedNormal = normalize(normal);
      vHeight = transformed.z;
    `
        );

        shader.fragmentShader = shader.fragmentShader.replace(
          "#include <common>",
          `
      #include <common>
      uniform vec3 uRiseColor;
      uniform float uRiseTime;
      varying float vHeight;
      varying vec3 vTransformedNormal;
      
      vec3 riseLine() {
        float smoothness = 1.8;
        float speed = uRiseTime;
        bool isTopBottom = (vTransformedNormal.z > 0.0 || vTransformedNormal.z < 0.0) && vTransformedNormal.x == 0.0 && vTransformedNormal.y == 0.0;
        float ratio = isTopBottom ? 0.0 : smoothstep(speed, speed + smoothness, vHeight) - smoothstep(speed + smoothness, speed + smoothness * 2.0, vHeight);
        return uRiseColor * ratio;
      }
    `
        );
        shader.fragmentShader = shader.fragmentShader.replace(
          "#include <dithering_fragment>",
          `
      #include <dithering_fragment>
      gl_FragColor = gl_FragColor + vec4(riseLine(), 1.0);
    `
        );
        renderList.push((time) => {
          shader.uniforms.uRiseTime.value = time * 30.0;
        });
      };

      // 扩散波效果
      const applySpreadShader = (shader) => {
        shader.uniforms.uSpreadTime = { value: 0 };
        shader.uniforms.uSpreadColor = { value: new THREE.Color("#9932CC") };

        shader.vertexShader = shader.vertexShader.replace(
          "#include <common>",
          `
      #include <common>
      varying vec2 vTransformedPosition;
    `
        );
        shader.vertexShader = shader.vertexShader.replace(
          "#include <begin_vertex>",
          `
      #include <begin_vertex>
      vTransformedPosition = vec2(position.x, position.y);
    `
        );
        shader.fragmentShader = shader.fragmentShader.replace(
          "#include <common>",
          `
      #include <common>
      uniform vec3 uSpreadColor;
      uniform float uSpreadTime;
      varying vec2 vTransformedPosition;
      
      vec3 spread() {
        vec2 center = vec2(0.0);
        float smoothness = 60.0;
        float start = mod(uSpreadTime, 1800.0);
        float distance = length(vTransformedPosition - center);
        float ratio = smoothstep(start, start + smoothness, distance) - smoothstep(start + smoothness, start + smoothness * 2.0, distance);
        return uSpreadColor * ratio;
      }
    `
        );
        shader.fragmentShader = shader.fragmentShader.replace(
          "#include <dithering_fragment>",
          `
      #include <dithering_fragment>
      gl_FragColor = gl_FragColor + vec4(spread(), 1.0);
    `
        );
        renderList.push((time) => {
          shader.uniforms.uSpreadTime.value = time * 200.0;
        });
      };
      // 扫光
      const applySweepShader = (shader) => {
        shader.uniforms.uSweepTime = { value: 0 };
        shader.uniforms.uSweepColor = { value: new THREE.Color("#00FFFF") };

        shader.vertexShader = shader.vertexShader.replace(
          "#include <common>",
          `
      #include <common>
      varying vec2 vSweepPosition;
    `
        );
        shader.vertexShader = shader.vertexShader.replace(
          "#include <begin_vertex>",
          `
      #include <begin_vertex>
      vSweepPosition = vec2(position.x, position.y);
    `
        );
        shader.fragmentShader = shader.fragmentShader.replace(
          "#include <common>",
          `
      #include <common>
      uniform vec3 uSweepColor;
      uniform float uSweepTime;
      varying vec2 vSweepPosition;
      
      vec3 sweep() {
        vec2 center = vec2(0.0);
        float smoothness = 60.0;
        float start = mod(uSweepTime, 1800.0) - 800.0;
        float ratio = smoothstep(start, start + smoothness, vSweepPosition.x) - smoothstep(start + smoothness, start + smoothness * 2.0, vSweepPosition.x);
        return uSweepColor * ratio;
      }
    `
        );
        shader.fragmentShader = shader.fragmentShader.replace(
          "#include <dithering_fragment>",
          `
      #include <dithering_fragment>
      gl_FragColor = gl_FragColor + vec4(sweep(), 1.0);
    `
        );
        renderList.push((time) => {
          shader.uniforms.uSweepTime.value = time * 160.0;
        });
      };

      /* ==========  4. 模型处理映射  ========== */
      const modelHandlerMap = {
        CITY_UNTRIANGULATED: (model, group) => {
          const { geometry, position, material } = model;
          const lineMat = new THREE.LineBasicMaterial({ color: "#2685fe" });
          const lineBox = new THREE.LineSegments(
            new THREE.EdgesGeometry(geometry, 1),
            lineMat
          );
          lineBox.position.copy(position);
          lineBox.rotateX(-Math.PI / 2);
          group.add(lineBox);

          material.onBeforeCompile = (shader) => {
            material.color = new THREE.Color("#0e233d");
            material.transparent = true;
            material.opacity = 0.9;
            applyGrowShader(shader);
            applyRiseShader(shader);
            applySpreadShader(shader);
            applySweepShader(shader);
          };
          lineMat.onBeforeCompile = (shader) => applyGrowShader(shader);
        },
        LANDMASS: (model) => {
          const m = model.material;
          m.color = new THREE.Color("#040912");
          m.transparent = true;
          m.opacity = 0.8;
        },
        ROADS: (model) => {
          model.material.color = new THREE.Color("#292e4c");
        },
      };

      /* ==========  5. 加载城市 FBX  ========== */
      new FBXLoader().load(
        FILE_HOST + "models/fbx/shanghai.FBX",
        (cityScene) => {
          const group = new THREE.Group();
          cityScene.children.forEach((item) => {
            const cloned = item.clone();
            modelHandlerMap[cloned.name]?.(cloned, group);
            group.add(cloned);
          });
          scene.add(group);
        }
      );

      /* ==========  6. 渲染循环  ========== */
      const clock = new THREE.Clock();
      function animate() {
        renderList.forEach((fn) => fn(clock.getElapsedTime()));
        requestAnimationFrame(animate);
        controls.update();
        renderer.render(scene, camera);
      }
      animate();

      /* ==========  7. 窗口缩放  ========== */
      window.addEventListener("resize", () => {
        renderer.setSize(box.clientWidth, box.clientHeight);
        camera.aspect = box.clientWidth / box.clientHeight;
        camera.updateProjectionMatrix();
      });
    </script>
  </body>
</html>
